#include <cassert>

#include "CircularBuffer.h"
#include "BufferErrorPolicies.h"

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::CircularBuffer(size_t size) : 
	m_pData(new T[size]),
	m_iSize(size),
	m_iContentSize(0),
	m_iHead(0),
	m_iTail(0)
{
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::~CircularBuffer()
{
	delete [] data;
	data = nullptr;
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
T& CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::operator[](size_t idx)
{
	assert(idx < m_iContentSize);
	if (empty()) 
		return ReadErrorPolicy::HANDLE_ERROR();
	return m_pData[idx];
}
	
template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
const T& CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::operator[](size_t idx) const
{
	assert(idx < m_iContentSize);
	if (empty()) 
		return ReadErrorPolicy::HANDLE_ERROR();
	return static_cast<const T&>(m_pData[idx]);
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
size_t CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::size() const
{
	return m_iSize;
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
bool CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::empty() const 
{
	return (m_iContentSize == 0);
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
T& CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::front()
{
	if (empty()) 
		return ReadErrorPolicy<T>::HANDLE_ERROR();
	return m_pData[m_iHead];
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
const T& CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::front() const
{
	if (empty()) 
		return ReadErrorPolicy<T>::HANDLE_ERROR();
	return static_cast<const T&>(m_pData[m_iHead]);
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
T& CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::back()
{
	if (empty()) 
		return ReadErrorPolicy<T>::HANDLE_ERROR();
	return m_pData[m_iTail];
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
const T& CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::back() const
{
	if (empty()) 
		return ReadErrorPolicy<T>::HANDLE_ERROR();
	return static_cast<const T&>(m_pData[m_iTail]);
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
void CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::pop_front()
{
	increment_head();
}

template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
void CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::push_back(const T& value)
{
	if (m_iContentSize == 0)
	{
		m_pData[m_iHead] = value;
		m_iTail = m_iHead;
		++m_iContentSize;
	}
	else if (m_iContentSize != m_iSize)
	{
		increment_tail();
		m_pData[m_iTail] = value;
	}
	else 
	{
		// policy must return true if oldest should be overwritten
		if (WriteErrorPolicy<T>::HANDLE_ERROR())
		{
			increment_head();
			increment_tail();
			m_pData[m_iTail] = value;
		}
	}
}
	
template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
void CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::increment_tail()
{
	++m_iTail;
	++m_iContentSize;
	if (m_iTail == array_size_) m_iTail = 0;
}
	
template<
	class T,
	template<class> class WriteErrorPolicy,
	template<class> class ReadErrorPolicy
>
void CircularBuffer<T, WriteErrorPolicy, ReadErrorPolicy>::increment_head()
{
	// precondition: !empty()
	++m_iHead;
	--m_iContentSize;
	if (m_iHead == m_iSize) m_iHead = 0;
}